import os, re, time, hashlib
import cPickle as pickle

# Path to Database, for global access...
ROOT = ""

# For more information on GNUdotCOM's Versioning Convention:
# http://talkbox.gnusnotunix.com/Source/GNUdotCOM-Versioning-Conventions.txt
version = (0,9,8)
__changelog__ = """
Version History:
	0.9.9 Final RC Revision - P/P.S Revision
	0.9.8 Final Release Candidate - "ChomsPy" Revision (Name Change)
	0.9.5 Release Candidate 2 - "Binary" Revision
	0.9.0 Release Candidate 1 - "SiteApp" Revision
	0.8.0 Beta Release
	0.5.0 Alpha Release

0.9.9 Features:
	* Code optimization, and error handling improvements.
	* Backup Feature reimplimented.
	* P/P.S Command Engine in place.
		See Jar.ExecuteCommand.__doc__ for more information.
	* User Module touched up.  Creates a userfile in the JarRoot directory.

0.9.8 Features:
    * All data storage in Binary, with efficient compression.
    * Name changed, and documentation updated to reflect new project name of
        "ChomsPy: Data Acquisition Device"

    ** Design refocus **
        ChomsPy will be shifting its focus on providing adaptive, flexible, speedy
        and customizable storage based on the "Data Acquisition Device" concept.
        Data is processed only for proper physical storage, and binary conversion,
        allowing for data items to be stored independant of their classifications.
        ChomsPy is going to focus on providing an efficient means of data storage,
        and retrieval - providing relative data storage specified by the application.

0.9.5 Features:
	* ChomsPy now reads/writes all data in binary mode for improved performance.
	* New naming conventions for ease of use.

Updates:
    0.9.8
    * Naming Convention updates:
        To fit in with Pyaget, rather than the intended project of SiteApp, a new final
        naming convention will be used, which should be more in line with the front-end
        which is now designed to utilize it, as well as future plans for a scripting
        engine.
    * New Design Model: Data Acquisition Device
        Fitting in with Chomsky's Theories around Linguistics is the idea of a LAD, or
        language acquisition device.  In this way, ChomsPy aims to be a Data Acquisition
        Device - neutral to the stimuli, utilizing whatever parts the front-end wishes
        to.
        This revision ushers in the change over in both naming convention, and project
        goals, as the database system will now model itself after a more cognitive model.


    0.9.5
    * Naming Convention updates:
        Functions and properties use a new naming convention.
        This will also ease the use of ChomsPy by making commands easier to remember.
            Methods:
                Jar
                    CreateTable
                    DeleteTable
                    Backup
                    UpdateTableList
                Table
                    Index
                    Get
                    Put
                    Batch (replaces grab)
                    Delete
                User
                    Add
                    Remove
                    ChangePassword
                    ChangeUserLevel
                    Validate
                    ReloadUsers
                    UpdateUsers

            Properties have remained the same, and are all lower case.

	* Binary mode is now being used for data items, and should provide for a significant
		performance boost.  With this mode, its feasible, though not recommended, to store
		converted binary strings in data items for images, and music.

Removed:
	* Deprecated functions due to Naming changes.
	* Grab Method, replaced by more efficient Batch Method.

Checklist for Release:
    * ErrorHandling -- Highest Priority 50%
        This is where the DAD method comes into play.  Data storage is done with
        as little effort as possible, yet should not be damaging to itself.
        Provide better error handling with more useful and accurate reports.
    * Documentation -- Moderate Priority 80%
        Checking format for PyDoc, as well as for HTML Translations.
        Providing as many example uses as possible, with and without front-ends.

Future Plans:
	MySQL, PostGRESQL, Oracle, MS SQL Integration
		* Convert SQL to PJAR, and PJAR to SQL
		* CrossReferencer Module, which will use SQL Storage for indexing PJAR data items.
		* TableMaker Module to use PJAR data items as Table Templates
	Indexing and Listing Improvements
		* Refining the Indexing to include checksums of data items for verification,
			multi-layered indexes for cross-referencing data items.
	Web-based Administration Panel
		* Create, Edit and Delete Jars, Tables, and Data Items, and more.

"""

__doc__ = """
ChomsPy: Data Acquisition Device

Created: December 2008, by GNUdotCOM/SnohomaNET
Maintainer: G. John Schutte (john@gnusnotunix.com)
Website: http://ChomsPy.snohoma.net
License: GPLv2

Description:
Relative Database System based on Python "Pickle" files for binary compressed storage and organization of data.
Databases (aka Jars) are directories, which hold directories which serve as Tables.
Data is stored as a Pickle file (a PJAR), with a unique identifier (aka uid) and data type for its file name.
It also contains a User Management System which is for quick authentication, but is not required to use the Database.

ChomsPy has 3 Subclasses:
	Jar = Database Management.  Create, Delete, Backup and Update Tables (not their data).
	Table = Table Management. Create, Delete, Sort and Retrieve data items from Table.
	User = User Management. Optional. Create, Delete, and Validate users with special Pickle File.

usage:
import ChomsPy

c = "Get/Cars/Dodge/Challenger"
j = ChomsPy.Jar("/path/to/jars","jar")
x = j.ExecuteCommand(c)
print x

"""

class Jar:
	"""
	ChomsPy.Jar

	Description:
	Loads a "Data Jar" for Organizational Tables, and
	Data Items.  Data Jars can be thought of as databases
	however, they are formless in general, where most
	databases are driven on storage structure, Jars
	are driven on semantic/pragmatic storage, relative
	to the application.

	usage:
	p = ChomsPy.Jar(path, database)

	returns:
	Jar Object
	"""

	def __init__(self, path, database="default"):
		global ROOT
		if os.path.exists(path + os.sep + database):
			self.JAR = database
		else:
			os.mkdir(path + os.sep + database)
			self.JAR = database
		
		# Clean any stray slashes...
		path = path.rstrip(os.sep)
		# Instance path to Jar Directory...
		self.JAR_PATH = path + os.sep + database
		# Organizational Tables List...
		self.TABLES = []
		# Set global path to Jar Directory variable...
		ROOT = self.JAR_PATH
		# write_level
		#	Level of user to write to the db.
		#	Default Order:
		#		0 = General
		#		1 = Read
		#		2 = Read/Write
		#		3 = Private
		self.WRITE_LEVEL = 2

		# Define the userfile for the Root...
		self.USERFILE = path + os.sep + "userfile"

		# Populate the tables list...
		self.__UpdateTableList()

	def CreateTable(self, table):
		# Verified!
		try:
			t = self.JAR_PATH + os.sep + table
			if os.path.exists(t):
				x = 0
			else:
				os.mkdir(t)
				x = 1
				self.__UpdateTableList()
			return x
		except:
			return -1
	
	def DeleteTable(self, table):
		# Verified!
		try:
			t = self.JAR_PATH + os.sep + table
			if os.path.exists(t):
				os.rmdir(t)
				x = 1
			else:
				x = 0
			self.__UpdateTableList()
			return x
		except:
			return -1
		
	def Backup(self, tables=0, compression="gz"):
		"""
		Back-up Table/Database Data to an archive

		usage:
		msg = p.Backup(0, "gz")
		Tables = Tupple list of tables to backup, or 0 for all tables.

		returns:
		Status of archive creation, or archive name if successful.
		"""
		import tarfile
		if compression == "gz":
			compression = "w:gz"
			com_ext = ".brain.gz"
		elif compression == "bz":
			compression = "w:bz"
			com_ext = ".brain.bz"
		else:
			compression = "w:gz"
			com_ext = ".brain.gz"
		tar_name = time.strftime("%m%d%Y_%H%M%S", time.localtime()) + com_ext
		tfile = tarfile.open(self.JAR_PATH + os.sep + tar_name, compression)
		if tables == 0:
			# Back everything up...
			tfile.add(self.JAR_PATH, "database")
		else:
			for tbl in tables:
				tfile.add(self.JAR_PATH + os.sep + tbl, tbl)
		tfile.close()

	def __UpdateTableList(self):
		"""
		Update Table Listing

		usage:
		p.UpdateTableList()

		returns:
		Nothing.  Instead it populates the Table listing.
		"""
		# Reset the Table list...
		self.TABLES = []
		# Check Jar directory for Tables...
		for i in os.listdir(self.JAR_PATH):
			# For each item in the Jar directory...
			if os.path.isdir(self.JAR_PATH + os.sep + i):
				# If its a Table, add it to the list...
				self.TABLES.append(i)
			else:
				# Otherwise pass...
				pass


	def DAD(self, c, user="", passwd="", data=""):
		"""
		Chomspy.Jar.DAD
		"The Data Acquisition Device"

		Description:
		Takes in a p/s.s formatted string, converts it
		to a p/s.s formatted dictionary and then
		executes the p/s.s formatted dictionary
		and supplies "data" for input commands. Uses
		user and passwd if inserting, or deleting data.

		Usage:
		j.DAD(command,formdata,u,p)
		
		Example:
		j.DAD("Admin/Add/bob.drowssap", "admin", "admin")
		j.DAD("Admin/Level/bob.2", "admin", "admin")
		j.DAD("Admin/Create/Cars", "admin", "admin")
		
		datadict = { "Doors" : "2", "Type" : "Manual", "Engine" : "Hemi V6" }
		j.DAD("Put/Cars/Dodge/Challenger","bob","drowssap",datadict)
		j.DAD("Semantic/Learn:GetChallenger,Get/Cars/Dodge/Challenger")
		j.DAD("Semantic/GetChallenger")
		j.DAD("Get/Cars/Dodge/GetChallenger")
		
		
		Returns:
		Status, or Result of Command.
		"""
		# Incomplete features...
		# 	Index, Semantics, Admin
		
		# Parse P/P.S Command...
		c = self.ParseCommand(c)
		
		# Check Method being called...
		if c.has_key("method"):
			# Load Table according to Method...
			if c["method"] == "Semantic":
				# Semantic only uses the _semantic Table...
				t = Table("_semantic")
			elif c["method"] == "Index":
				# Index may not have a Table set...
				if c["table"] == 0:
					# Don't load a table...
					pass
				else:
					# Load the Table...
					t = Table(c["table"])
			elif c["method"] == "Admin":
				pass
				
			else:
				# For all other Methods, load indicated table...
				t = Table(c["table"])
		else:
			# Bad command, return error.
			return -2

		# Return value placeholder...
		h = ""
		u = User(self.USERFILE)
		
		# Execute Table.Get Method...
		if c["method"] == "Get":
			# Get the data item...
			h = t.Get(c["uid"],c["type"])
			# Check for key request...
			if c.has_key("key"):
				# If true, return that value...
				h = h[c["key"]]

		# Execute Put...
		elif c["method"] == "Put":
			if u.Validate(user, passwd) == self.WRITE_LEVEL:
				# Valid user...
				# Check for key request...
				if c.has_key("key"):
					# If true, get data item...
					h = t.Get(c["uid"],c["type"])
					# Replace key field...
					h[c["key"]] = data
					# Put the data item back...
					h = t.Put(c["uid"],c["type"],h,"y")
				else:
					# Put the data item into the database...
					h = t.Put(c["uid"],c["type"],data,"y")
			else:
				# Invalid User...
				h = 0
		# Execute Delete...
		elif c["method"] == "Delete":
			# Check for key request...
			if c.has_key("key"):
				# If true, get data item...
				h = t.Get(c["uid"],c["type"])
				# Remove key field...
				del h[c["key"]]
				# Put the data item back...
				h = t.Put(c["uid"],c["type"],h,"y")
			else:
				# Delete the data item...
				h = t.Delete(c["uid"],c["type"])

		# Execute Collate...
		elif c["method"] == "Collate":
			h = t.Collate(c["type"], c["count"], (c["mode"],c["filter"]), c["order"])

		elif c["method"] == "Semantic":
			# Create/Retrieve Semantic Association
			# 	Semantic/[Learn|Forget|List]:Expression,Statement
			# 	Semantic/Expression
			#
			# Check for action...
			if c.has_key("action"):
				# If there is an action, parse further...
				if c["action"] == "Learn":
					if u.Validate(user, passwd) == 2:
						#Valid User...
						# Learn a new Semantic Association...
						h = t.Get(self.JAR,"semantic")
						# If the file doesn't exist...
						if h == 0:
							# There is a semantic file, create new one...
							h = {}
						# Insert the Semantic Association into the Data Item...
						h[c["expression"]] = c["statement"]
						# Write the Semantic data item.
						h = t.Put(self.JAR,"semantic",h,"y")
					else:
						# Invalid User...
						h = -4
					
				elif c["action"] == "Forget":
					# Forget a Semantic Association...
					if u.Validate(user, passwd) == 2:
						# Valid User...
						# Get Semantic Associations...
						h = t.Get(self.JAR,"semantic")
						# Find the semanic...
						if h.has_key(c["expression"]):
							# Key found, remove it...
							del h[c["expression"]]
							# Write Changes back to Semantic file...
							h = t.Put(self.JAR,"semantic",h,"y")
						else:
							# No Key found...
							h = -3
					else:
						#Invalid User..
						h = -4
					
				elif c["action"] == "List":
					# List Semantic Associations...
					h = t.Get(self.JAR, "semantic")
				
				else:
					pass
				
			else:
				# No action, retrieve Semantic Association...
				h = t.Get(self.JAR,"semantic")
				# Get the statement...
				h = self.DAD(h[c["expression"]])
			# Return the statement.
			return h

		elif c["method"] == "Index":
			# Returns an index of tables, types or items...
			# Index[/Table[/Type]]
			
			# Check command for Table...
			if c.has_key("table"):
				# Check if Table is 0...
				if c["table"] == 0:
					# No Table given, list all Tables in Jar...
					self.__UpdateTableList()
					h = self.TABLES
				else:
					# Table provided, check for Type...
					h = t.Index(c["type"])
			return h
			
		elif c["method"] == "Admin":
			# Administrative Methods...
			if c.has_key("parameter"):
				if c["parameter"] == "Create":
					# Create Table in Jar...
					if u.Validate(user, passwd) == 2:
						# Valid user, Create Table...
						h = self.CreateTable(c["argument"])
					else:
						# Invalid User...
						h = -4
						
				elif c["parameter"] == "Destroy":
					# Remove Table (and contents) from Jar...
					if u.Validate(user, passwd) == 2:
						# Valid User, Destroy Table...
						h = self.DeleteTable(c["argument"])
					else:
						# Invalid User...
						h = -4
				
				elif c["parameter"] == "Add":
					# Add User...
					# Validate User/Passwd for creating new user...
					if u.Validate(user, passwd) == 2:
						# Valid User...
						if "." in c["argument"]:
							# Separate User and Password from Argument...
							usr, pw = c["argument"].split(".")
							# Prevent null password...
							if pw == "":
								pw = "none"
							# Add User...
							h = u.Add(usr, pw,1)
						else:
							# Invalid Command...
							h = -3
					else:
						# Invalid User...
						h = -4
					
				elif c["parameter"] == "Remove":
					# Remove User...
					# Validate User/Passwd for creating new user...
					if u.Validate(user, passwd) == 2:
						# Valid User...
						h = u.Remove(c["argument"])
					else:
						# Invalid User...
						h = -4
					
				elif c["parameter"] == "Level":
					if u.Validate(user, passwd) == 2:
						# Valid User...
						if "." in c["argument"]:
							# Separate User and Level from Argument...
							usr, lvl = c["argument"].split(".")
							# Convert lvl to Int...
							lvl = int(lvl)
							# Change level...
							h = u.ChangeUserLevel(usr,lvl)
						else:
							h = -3
					else:
						h = -4
					
				elif c["parameter"] == "Password":
					if u.Validate(user, passwd) == 2:
						# Valid User...
						if "." in c["argument"]:
							# Separate User and Password from Argument...
							usr, pw = c["argument"].split(".")
							# Precent null password...
							if pw == "":
								pw = "none"
							# Change Password...
							h = u.ChangePassword(usr,pw)
						else:
							h = -3
					else:
						h = -4
						
				elif c["parameter"] == "Validate":
					if "." in c["argument"]:
						usr, pw = c["argument"].split(".")
						h = u.Validate(usr,pw)
					else:
						h = -3
					
				elif c["parameter"] == "Backup":
					if u.Validate(user, passwd) == 2:
						# Valid User...
						if c["argument"] != 0:
							c["argument"] = c["argument"].split(",")
						h = self.Backup(c["argument"])
					else:
						# Invalid User...
						h = -4
				else:
					# Invalid Parameter...
					h = 0
			return h
			
		else:
			# Invalid Method...
			h = 0

		return h

	def ParseCommand(self, c):
		"""
		ChomsPy.ParseCommand
		
		Takes string formatted in "Pragmatic/Semantic.Syntax" ("p/s.s") and
		returns it in dictionary format, suitable for execution in
		ExecuteCommand.
		
		Returns:
		Dictionary for ExecuteCommand, or 0 if error.
		"""
		
		# Command Dictionary...
		x = {}
		
		# Semantic is non-standard, pre-process Semantic statements...
		if c.startswith("Semantic"):
			definition = ""
			# Check for Statement...
			if "," in c:
				# There is a Statement, separate it from the Action/Expression...
				command, definition = c.split(",")
				# Set the Action/Expression to "c" for further processing...
				c = command.split("/")
			else:
				# There is no Statement, pass on as usual...
				c = c.split("/")
		else:
			# Non-Semantic Method, process as usual...
			c = c.split("/")
		
		# Determine Method to be used...
		x["method"] = c[0]
		
		# Parse Command based on Method...
		if x["method"] == "Semantic":
			# Non-standard Assignment...
			try:
				# Check for Action...
				if c[1] == "List":
					# Process List...
					x["action"] = c[1]
					x["expression"] = 0
					x["statement"] = 0
					
				else:
					if ":" in c[1]:
						# There is an Action, separate it from the Expression and Statement...
						a, s = c[1].split(":")
						# Store Action, Expression and Statement...
						x["action"] = a
						x["expression"]  = s
						x["statement"] = definition
					else:
						x["expression"] = c[1]
			except:
				x = -1
				
		elif x["method"] == "Index":
			# Check for Table...
			try:
				# If there's a Table, store it...
				x["table"] = c[1]
			except:
				# Otherwise set Table to 0..
				x["table"] = 0
			# Check for Type...	
			try:
				# If there is a Type, store it...
				x["type"] = c[2]
			except:
				# Otherwise set Type to 0...
				x["type"] = 0
				
		elif x["method"] == "Admin":
			# Check for Parameter...
			try:
				# If there's a Parameter, store it..
				x["parameter"] = c[1]
			except:
				# Otherwise the command is invalid..
				x["method"] = 0
			# Check for Argument...
			try:
				# If there's an Argument, store it...
				x["argument"] = c[2]
			except:
				# Give argument 0 value...
				x["argument"] = 0
				
		else:
			# Standard Assignment for Table, and Type...
			try:
				x["table"] = c[1]
			except:
				x["table"] = 0
				x["method"] = 0
			try:
				x["type"] = c[2]
			except:
				x["type"] = 0
				x["method"] = 0
			
			if x["method"] == "Get" or x["method"] == "Put" or x["method"] == "Delete":
				# Table/Type/Item.Field
				try:
					if "." in c[3]:
						uid, kid = c[3].split(".")
						x["uid"] = uid
						x["key"] = kid
					else:
						x["uid"] = c[3]
				except:
					x["uid"] = 0
					x["method"] = 0
			
			elif x["method"] == "Collate":
				# Table/Type/Count/Order/Mode.Filter
				try:
					x["count"] = c[3]
				except:
					x["count"] = 0
				
				try:
					x["order"] = c[4]
				except:
					x["order"] = "d"
				
				try:
					if "." in c[5]:
						mode, filter = c[5].split(".")
						x["mode"] = mode
						x["filter"] = filter
					else:
						x["mode"] = 0
						x["filter"] = 0
				except:
					x["mode"] = 0
					x["filter"] = 0
			
			elif x["method"] == "Index":
				# Table/Type
				x = x
				
			elif x["method"] == "Admin":
				# Parameter = Create|Destroy|Add|Remove|Level|Password|Validate
				# Parameter/Argument/Value
				pass
				
			else:
				# Invalid Method, return error...
				x = -2
		return x
	
class Table:
	def __init__(self, table):
		global ROOT
		self.TABLE = table
		self.CACHE = {}
		self.TABLE_PATH = ROOT + "/" + table
		self.DATA_ITEMS = self.Index(0)

	def Index(self, types):
		"""
		Create a Index of data in the Table

		usage:
		i = t.index(0) (or) i = t.index("post")

		returns:
		List of data items.  0 Returns all types, otherwise only specified types are returned in the list.
		"""
		l = []
		items = os.listdir(self.TABLE_PATH)
		for item in items:
			if types == 0:
				l.append(item)
			else:
				if item.endswith("." + types):
					u, d = item.split(".")
					l.append(u)
				else:
					pass
		return l

	def Put(self, uid, dtype, data, overwrite="n"):
		"""
		Create or Modify a data item
			uid = Unique ID for Data Item
			dtype = Data Type for the Item
			data = Dictionary data to be put into the Pickle
			overwrite = (y)es, (n)o
					Yes - Overwrites existing file if needed
					No - Aborts if file already exists

		usage:
		msg = t.Put("apple","word",word_dict,"y")

		returns:
		Status of the operation (integer: 0 - Failed, 1 - Success)
		"""
		fpath = self.TABLE_PATH + "/" + uid + "." + dtype
		if os.path.exists(fpath):
			if overwrite is "y":
				f = open(fpath, "wb")
				pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
				f.close()
				self.DATA_ITEMS = self.Index(0)
				return 1
			else:
				return 0
		else:
			f = open(fpath, "wb")
			pickle.dump(data, f, pickle.HIGHEST_PROTOCOL)
			f.close()
			self.DATA_ITEMS = self.Index(0)
			return 1

	def Get(self, uid, dtype):
		"""
		Retrieve Data Item from Table

		usage:
		d = t.Get("apple", "word")

		returns:
		Dictionary of Data Item, or 0 if there's no Data Item.
		"""
		fpath = self.TABLE_PATH + os.sep + uid + "." + dtype
		if os.path.exists(fpath):
			f = open(fpath, "rb")
			jar = pickle.load(f)
			f.close()
			return jar
		else:
			return 0

	def Delete(self, uid, dtype):
		"""
		Delete Data Item from Table

		usage:
		msg = t.Delete("apple", "word")

		returns:
		Status of operation (integer: 0 - Failed, 1 - Success)
		"""
		fpath = self.TABLE_PATH + os.sep + uid + "." + dtype
		if os.path.exists(fpath):
			os.remove(fpath)
			return 1
		else:
			return 0

	def Collate(self, dtype, count=0, filters=("",""), order="d"):
		"""
		Retrieve a set of Data Items from the Table
			dtype = Data Type
			filters = Tupple list of UID parsers
					match = Fuzzy comparison on uid
					pattern = RegEx comparison on uid
					starts = Starts with characters on uid
					ends = Ends with characters on uid
			count = Number of Data Items to return
			order = (a)scending or (d)escending

		usage:
		d = t.Collate("word", ("match", "app"), 10, "d")

		returns:
		Dictionary of Data Items, listed like so:
			{ uid : { data item } }
		"""
		# Main List of data items...
		l = []
		self.DATA_ITEMS = self.Index(dtype)
		
		# Get list...
		for item in self.DATA_ITEMS:
			# Get unique id, and data type from the data listing...
			uid = item
			# If the dt matches the parameter...
			try:
				# Check filter mode for 'match' and compare uid to the filter string...
				if filters[0] == "match":
					if filters[1] in uid:
						# If it matches, add it to the list of data items...
						l.append(item)
					else:
						pass
				elif filters[0] is "pattern":
					pattern = re.compile(filters[1])
					if pattern.match(uid):
						l.append(item)
					else:
						pass
				# Check filter mode for 'starts' and compare the beginning of the uid to the filter string...
				elif filters[0] == "starts":
					if uid.startswith(filters[1]):
						# If it matches, add it to the list of data items...
						l.append(item)
					else:
						pass
				# Check filter mode for 'ends' and compare the ending of the uid to the filter string...
				elif filters[0] == "ends":
					if uid.endswith(filters[1]):
						# If it matches, add it to the list of data items...
						l.append(item)
					else:
						pass
				else:
					# Ignore filters...
					l.append(item)
			except:
				return 0
				
		# Check the order parameter and sort data item list appropriately...
		if order is "d":
			l.reverse()
		else:
			l.sort()
		# Check count against the length of the data item list, and set the count maximum accordingly...
		if count > len(l) or count == 0:
			count = len(l)
		# Setup list of items to be pulled...
		ilist = []
		# Get list of filtered items to be retrieved...
		for x in range(0, count):
			ilist.append(l[x])
		# Setup dictionary of items to be pulled into, and insert the list of items to be retrieved in order as "index"...
		idict = { "_index" : ilist }
		# Get data items, and insert into the dictionary...
		for x in range(0,count):
			uid = ilist[x]
			if "." in uid:
				uid, dt = uid.split(".")
			else:
				dt = dtype
				
			idict[uid] = self.Get(uid, dt)
		# Return Dictionary.
		return idict

class User:
	"""
	Optional Module for managing users, and validating them.

	usage:
	userdb = ChomsPy.User("/path/to/userfile")

	example:
	a = userdb.add("john","drowssap","1")
	b = userdb.passwd("john","changeme")
	c = userdb.change_level("john","2")
	user_level = userdb.validate("john","changeme")
	d = userdb.remove("john")

	(returns)
	a-d: 1 - Success, 0 - Fail
	user_level = Integer of user's level

	"""

	def __init__(self,userfile,enc="sha1"):
		"""
		Initialization checks for the "userfile" pickle existance and creates it if required.
			userfile = Path to the userfile pickle (ie.. /var/Jars/snohoma.net/userfile)
			enc = Encryption type (md5, sha, plain)

		userfile = { "user" : { "password" : "xxxx", "level" : x }, ... }
		"""
		
		self.userfile = userfile
		self.EN_TYPE = enc
		# Check for userfile parameter...
		if userfile == "":
			# Generate error, no userfile supplied...
			return 0
		else:
			# Check if the userfile exists...
			if os.path.exists(userfile):
				# Load User Pickle...
				f = open(userfile, "rb")
				# Store userfile path...
				self.userdb = pickle.load(f)
				f.close()
			else:
				# userfile doesn't exist, so create userfile...
				f = open(userfile, "wb")
				# Create user 'pjar' with password 'pjar' at level 0
				init_data = { "root" : { "password" :  self.Encrypt("chomspy",self.EN_TYPE), "level" : 2 } }
				pickle.dump(init_data, f, pickle.HIGHEST_PROTOCOL)
				f.close()
				# Pull userfile data from pickle into the userdb property...
				f = open(userfile, "rb")
				self.userdb = pickle.load(f)
				f.close()

	def Encrypt(self, s, t="sha1"):
		e = ""
		if t is "md5":
			e = hashlib.md5()
		else:
			e = hashlib.sha1()
			
		e.update(s)
		return e.hexdigest()

	def Add(self, user, passwd, level):
		"""
		Add a user.
		"""
		if user == "" or passwd == "":
			# Blank data, generate error...
			msg = -1
		else:
			passwd = self.Encrypt(passwd,self.EN_TYPE)
			if self.userdb.has_key(user):
				# User already exists, generate error...
				msg = 0
			else:
				# Create user...
				self.userdb[user] = { "password" : passwd, "level" : level }
				# Update userfile...
				msg = self.UpdateUsers()
				if msg == 1:
					# Reload userdb...
					msg = self.ReloadUsers()
				else:
					pass
		return msg

	def Remove(self, user):
		"""
		Remove a user.
		"""
		if user == "":
			# Blank data, generate error...
			msg = -1
		else:
			if self.userdb.has_key(user):
				# User exists, remove from userdb...
				del self.userdb[user]
				# Update userfile...
				msg = self.UpdateUsers()
				if msg == 1:
					# Reload userdb...
					msg = self.ReloadUsers()
				else:
					pass
			else:
				# User doesn't exist, generate error...
				msg = 0
		return msg

	def ChangePassword(self, user, passwd):
		"""
		Change a user's password.
		"""
		if user == "" or passwd == "":
			# Blank data, generate error...
			msg = -1
		else:
			if self.userdb.has_key(user):
				# User exists, change password...
				self.userdb[user]["password"] = self.Encrypt(passwd,self.EN_TYPE)
				# Update userfile...
				msg = self.UpdateUsers()
				if msg == 1:
					# Reload userdb...
					msg = self.ReloadUsers()
				else:
					pass
			else:
				# User doesn't exist, generate error...
				msg = 0
		return msg

	def ChangeUserLevel(self, user, level):
		"""
		Change a user's level.
		"""
		if user == "" or level == "":
			# Blank data, generate error...
			msg = -1
		else:
			if self.userdb.has_key(user):
				# User exists, change level...
				self.userdb[user]["level"] = level
				# Update userfile...
				msg = self.UpdateUsers()
				if msg == 1:
					# Reload userdb...
					msg = self.ReloadUsers()
				else:
					pass
			else:
				msg = 0
		return msg

	def Validate(self, user, passwd):
		"""
		Validate a user

		returns:
		Integer - User's Level, or -1 for failed login.
		"""
		if user == "" or passwd == "":
			# Blank data, generate error...
			msg = -1
		else:
			if self.userdb.has_key(user):
				# Encrypt password...
				passwd = self.Encrypt(passwd, self.EN_TYPE)
				# User exists, compare passwords...
				if passwd == self.userdb[user]["password"]:
					# Passwords match, return user level...
					msg = self.userdb[user]["level"]
				else:
					# Passwords don't match, return error...
					msg = -2
			else:
				# User doesn't exist, generate error...
				msg = -3
		return msg

	def ReloadUsers(self):
		"""
		Reload userdb index
		"""
		if self.userdb == 0:
			# No userfile loaded, or invalid userfile specified...
			msg = 0
		else:
			# Valid userdb loaded, reload from userfile...
			f = open(self.userfile, "rb")
			self.userdb = pickle.load(f)
			f.close()
			msg = 1
		return msg

	def UpdateUsers(self):
		"""
		Update userfile with userdb index
		"""
		if self.userdb == 0 or self.userfile == "":
			# No userfile loaded, or invalid userfile specified...
			msg = 0
		else:
			# Valid userdb loaded, reload from userfile...
			f = open(self.userfile, "wb")
			pickle.dump(self.userdb, f, pickle.HIGHEST_PROTOCOL)
			f.close()
			msg = 1
		return msg
